#!/usr/bin/python
# -*- coding: utf8 -*-
# 
# Copyright 2007 Joe Friedrichsen <pengi.films@gmail.com>
# 
# This file is part of bbgun.
# 
# bbgun is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 
# bbgun is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with bbgun; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

"""Convert phpbb forums into WordPress blog posts

"""

import re
import sys
import ConfigParser
from optparse import OptionParser

try:
    from bbgun import BBDatabase
except ImportError:
    print "Cannot find bbgun module BBDatabase! Please install it and try again."
    sys.exit(1)

class bbgunError(Exception): pass
class ConfigError(bbgunError): pass

def createDefaultConfig(file, sections):
    """Create a default config file for bbgun and leave it open
    
    Keyword args:
    file -- a file object for the config file
    sections -- a dictionary of sections and their options
        sections.keys() == the names of the sections to add
        sections.values() == a list of tuples of (option_name, option_value)
    
    NOTE: Passwords in this file are kept in PLAIN TEXT, please secure
    them. One way is to reduce this file's permissions:
        $ chown $(whoami) bbgun.config
        $ chgrp $(whoami) bbgun.config
        $ chmod 700 bbgun.config
    These commands will only let __you__ read and modify this file (or
    anyone who has logged in as you).
    
    Databases: both databases have five attributes:
        type -- the type of the database
        name -- the name of the database
        host -- the host that has the database (this is most often 'localhost')
        user -- the username with access to the database
        password -- the user's password
    
    """
    me = 'createDefaultConfig'
    
    config = ConfigParser.SafeConfigParser()
    for s in sections.keys():
        print "%s: adding section '%s'" % (me, s)
        config.add_section(s)
        for pair in sections[s]:
            print "%s: adding option '%s: %s'" % (me, pair[0], pair[1])
            config.set(s, pair[0], pair[1])
    
    config.write(file)

def variable_args(option, opt_str, value, parser):
    """Parse options that have a variable number of arguments
    
    Keyword args:
    option -- the option object currently being parsed
    opt_str -- the exact string found on the command line for the option
    value -- the current arguments read in for option
    parser -- the parser object doing all the work
    
    """
    me = 'variable_args'
    assert value is None
    value = []
    rargs = parser.rargs
    
    # Group all of the parser's options
    all_options = []
    for opt in parser.option_list:
        all_options = all_options + opt._short_opts + opt._long_opts
    
    while rargs:
        arg = rargs[0]
        # Stop if we hit an arg in the parser
        if arg in all_options:
            break
        else:
            value.append(arg)
            del rargs[0]
    
    print "%s: updating %s with values %s" % \
        (me, option.dest, value)
    setattr(parser.values, option.dest, value)

def main():
    me = 'main'

    # Setup the parser
    opt_parser = OptionParser(usage="%prog [options]", version="%prog 0.20",
        description="Move phpBB forums to WordPress blogs")
    # Define the command line arguments
    opt_parser.add_option("-t", "--threads",
            help="threads to translate (IDs or URLs)",
            dest="threads", action="callback", callback=variable_args)
    opt_parser.add_option("-p", "--posts",
            help="posts to translate (IDs or URLs)",
            dest="posts", action="callback", callback=variable_args)
    opt_parser.add_option("-c", "--categories",
            help="categories to add posts to (IDs or names)",
            dest="categories", action="callback", callback=variable_args)
    opt_parser.add_option("-a", "--authors",
            help="author mappings (ex: 'BB Author: WP Author')",
            dest="authors", action="callback", callback=variable_args)
    opt_parser.add_option("-q", "--quiet",
            help="set verbosity to 0 (only error messages)",
            dest="verbosity", action="store_const", const=0)
    opt_parser.add_option("-v", "--verbosity",
            help="0..9; 0 is errors only, 9 is full debug", 
            metavar="N", dest="verbosity", type="int")
    opt_parser.add_option("--verbose",
            help="set verbosity to 5 (frequent updates)",
            dest="verbosity", action="store_const", const=5)
    opt_parser.set_defaults(verbosity=1)
    
    # Grab the command line options
    (opts, args) = opt_parser.parse_args()
    
    print "main:", opts

    # Process the options
    # threads and posts can be either numerical IDs or URLs; format them to int
    threads = []
    if opts.threads:
        for t in opts.threads:
            if t.isdigit():
                # t is an ID
                threads.append(int(t))
            else:
                # a URL like .../modules.php?name=Forums&file=viewtopic&t=67
                topic = re.search('t=[0-9]+', t).group()
                threads.append(int(topic[2:]))
    
    posts = []
    if opts.posts:
        for p in opts.posts:
            if p.isdigit():
                # p is an ID
                posts.append(int(p))
            else:
                # p is a URL
                post = re.search('p=[0-9]+', p).group()
                posts.append(int(post[2:]))
    
    # authors is a mapping, format to a dictionary
    authors = {}
    if opts.authors:
        for a in opts.authors:
            mapping = a.split(':')
            authors[mapping[0].strip()] = mapping[1].strip()
    
    # Use the default category if none were given
    if not opts.categories:
        categories = ['DEFAULT']
    else:
        categories = opts.categories
    
    print "%s: threads = %s" % (me, threads)
    print "%s: posts = %s" % (me, posts)
    print "%s: categories = %s" % (me, categories)
    print "%s: authors = %s" % (me, authors)
    
    bb_authors = authors.keys()
    
    ## Read the config file for database info
    # Open the config file
    config_file_name = 'bbgun.config'
    config_file = open(config_file_name, 'ar+')
    
    # If the config file doesn't exist, it will have length 0
    if len(config_file.read()) == 0:
        print "No config file present, creating one..."
        database_sections = ['BB Database 1', 'WP Database 1']
        database_options =  [('type', 'db-type'), ('name', 'db-name'),
            ('host', 'db-host'), ('user', 'db-user'), ('password', 'user-pw') ]
        config_sections = {}
        for s in database_sections:
            config_sections[s] = database_options
        
        createDefaultConfig(config_file, config_sections)
    
    # Parse the config file
    config_file.seek(0)
    config = ConfigParser.SafeConfigParser()
    config.readfp(config_file)
    config_file.close()
    
    # Connect to the db's
    for database in config.sections():
        db = dict(config.items(database))
        if db['type'] == 'phpnuke':
            nuke = BBDatabase.PHPNukeDB(db['name'], db['host'],
                db['user'], db['password'])
        elif db['type'] == 'wpmu':
            wp = BBDatabase.WordPressDB(db['name'], db['host'],
                db['user'], db['password'])
        else:
            raise ConfigError, "Unrecognized database type: %s" % db['type']
    
    # Translate the threads
    for t in threads:
        # Grab the posts in the thread
        bb_posts = nuke.pullThread(t, bb_authors)
        # Limit them to the bb authors
        bb_posts = [p for p in bb_posts if p['author'] in bb_authors]
        # 
        wp_posts = []
        for post in bb_posts:
            # Find the bb author
            bb_auth = post['author']
            # Find the corresponding WP author
            wp_auth = authors[bb_auth]
            # Get that author's defualts
            if wp.authorExists(wp_auth):
                defaults = wp.getDefaults(wp_auth)
                # Translate the post
                wp_post = BBDatabase.WPPost(post, defaults)
                # Add it to the WP thread
                wp_posts.append(wp_post)
            else:
                print "Skipping author '%s': not in WordPress database." % \
                    wp_auth
        print len(wp_posts), len(bb_posts)
        wp.pushThread(wp_posts, defaults, categories)
    
    # Translate the posts
    for p in posts:
        # Grab the post
        bb_post = nuke.pullPost(p, bb_authors)
        # Limit it to the bb authors
        if bb_post['author'] not in bb_authors:
            break
        # Find the bb author
        bb_auth = bb_post['author']
        wp_auth = authors[bb_auth]
        if wp.authorExists(wp_auth):
            defaults = wp.getDefaults(wp_auth)
            wp_post = BBDatabase.WPPost(bb_post, defaults)
        wp.pushPost(wp_post, defaults, categories)
    
    # Close the db's
    nuke.close()
    wp.close()

if __name__ == '__main__':
    main()
